#include "VertexBuffer.h"
#include "../../Core/CoreGlobals.h"
#include "../../Utils/LogUtil.h"
#include "../../Templates/NewObject.h"
#include "../RenderState.h"
#include "../../Game/Process/RenderProcess.h"

VertexBuffer::Builder::Builder() noexcept = default;
VertexBuffer::Builder::~Builder() noexcept = default;
VertexBuffer::Builder::Builder(VertexBuffer::Builder const& rhs) noexcept = default;
VertexBuffer::Builder::Builder(VertexBuffer::Builder && rhs) noexcept = default;
VertexBuffer::Builder& VertexBuffer::Builder::operator=(VertexBuffer::Builder const& rhs) noexcept = default;
VertexBuffer::Builder& VertexBuffer::Builder::operator=(VertexBuffer::Builder && rhs) noexcept = default;

VertexBuffer::Builder& VertexBuffer::Builder::SetData(void* data) noexcept
{
	mImpl->data = data;
	return *this;
}

VertexBuffer::Builder& VertexBuffer::Builder::SetSize(VkDeviceSize size) noexcept
{
	mImpl->size = size;
	return *this;
}

VertexBuffer::Builder& VertexBuffer::Builder::SetRayUse() noexcept
{
	mImpl->isRayUse = true;
	return *this;
}

RefCountPtr<VertexBuffer> VertexBuffer::Builder::Build()
{
	assert(IsInRenderThread());
	return RefCountPtr<VertexBuffer>(NewObject<VertexBuffer>(*mImpl));
}

RefCountPtr<VertexBuffer> VertexBuffer::Builder::Build(RenderProcess * process, const std::string & name)
{
	assert(IsInRenderThread());
	VertexBuffer* vertexBuffer = NewObject<VertexBuffer>(*mImpl);
	process->AddBuffer(name, vertexBuffer);
	return RefCountPtr<VertexBuffer>(vertexBuffer);
}

void VertexBuffer::ReleaseResource()
{
	if (IsInRenderThread())
	{
		if (mProcess != nullptr)
		{
			mProcess->RemoveBuffer(mName, this);
		}
		DeleteObject<VertexBuffer>(this);
	}
	else
	{
		AsyncTask(GRenderThreadId, [this]() {
			if (mProcess != nullptr)
			{
				mProcess->RemoveBuffer(mName, this);
			}
			DeleteObject<VertexBuffer>(this);
			});
	}
}

VertexBuffer::VertexBuffer(BuilderDetails & builderDetails)
{
	mIsMultiple = false;

	mFlags = 0;
	mSize = builderDetails.size;
	mUsage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
	mSharingMode = VK_SHARING_MODE_EXCLUSIVE;
	mMemoryProperty = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;


	if (builderDetails.isRayUse)
	{
		mUsage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
	}


	mBuffers.resize(1, VK_NULL_HANDLE);
	mBufferMemorys.resize(1, VK_NULL_HANDLE);

	GVulkanRHI->CreateBuffer(mBuffers[0], mBufferMemorys[0], mSize, mUsage, mMemoryProperty, mSharingMode, mQueueFamilys);

	if (builderDetails.data != nullptr)
	{
		VkBuffer stagingBuffer;
		VkDeviceMemory stagingMemory;

		GVulkanRHI->CreateBuffer(stagingBuffer, stagingMemory, mSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

		void* data;
		VK_CHECK_RESULT(vkMapMemory(GVulkanRHI->GetLogicalDevice(), stagingMemory, 0, mSize, 0, &data));
		memcpy(data, builderDetails.data, mSize);
		vkUnmapMemory(GVulkanRHI->GetLogicalDevice(), stagingMemory);

		VkCommandBuffer commandBuffer = GRenderState->BeginSingleCommand();


		VkBufferCopy bufferCopy{};
		bufferCopy.srcOffset = 0;
		bufferCopy.dstOffset = 0;
		bufferCopy.size = mSize;
		vkCmdCopyBuffer(commandBuffer, stagingBuffer, mBuffers[0], 1, &bufferCopy);


		GRenderState->FlushSingleCommand(commandBuffer);

		vkDestroyBuffer(GVulkanRHI->GetLogicalDevice(), stagingBuffer, nullptr);
		vkFreeMemory(GVulkanRHI->GetLogicalDevice(), stagingMemory, nullptr);
	}
}
